// You do not need to alter or look at this code
// for the APC coursework. Just study the DateTest class

// $Id: Date.C,v 1.2 2002/10/18 05:56:15 tknarr Exp $
// Copyright (C) 2000, 2002
// Todd T. Knarr <tknarr@silverglass.org>

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

//
// Classname      : Date
//
// Author         : Todd Knarr
//
// Creation Date  : 29 Nov 1995
//
// Superclasses   : none
//
// Subclasses     : none
//
// Required files : Date.h
//
// Description    :
//
// Provides a Date class which represents dates as
// Julian day numbers ( days since 1 Jan 4713 BC ).
// This class can handle all dates from  1 Jan 4713BC to 31 Dec 4999AD.
//
// Note: Years AD are positive, years BC are negative. There is
// no year 0AD, it goes from 1BC to 1AD. A year of 0 will be treated
// as 1BC. No attempt is made to validate ranges. Physical errors
// will not result from insane day-month combinations like the 87th
// day of the 45th month, but the results will obviously not make
// much sense.
//
// Date conversion routines  by Eric bergman-Terrell, Computer Language,
//     Dec 1990.
// Alternate method by David Burki, CUJ Feb 1993.
//
// To Do:
// * Add << and >> operators for iostreams. They should probably output
//   and input the string form.
//

#include "stdafx.h"

#include "Date.h"

#include <time.h>

//
//  Function      : YmdToJd
//
//  Author        : Todd Knarr
//
//  Creation date : 29 Nov 1995
//
//  Parameters    : int year, month, day
//
//  Return values : long julian day
//
//  Description   :
//
// Internal routine that does the physical conversion
// from YMD form to Julian day number.
//
long Date::YmdToJd( const int iYear, const int iMonth, const int iDay )
{
    long jul_day;

#ifndef JULDATE_USE_ALTERNATE_METHOD

    int a,b;
    int year = iYear, month = iMonth, day = iDay;
    float year_corr;

    if ( year < 0 )
        year++;
    year_corr = (float)(( year > 0 ? 0.0 : 0.75 ));
    if ( month <= 2 )
    {
        year--;
        month += 12;
    }
    b = 0;
    if ( year * 10000.0 + month * 100.0 + day >= 15821015.0 )
    {
        a = year / 100;
        b = 2 - a + a / 4;
    }
    jul_day = (long) ( 365.25 * year - year_corr ) +
              (long) ( 30.6001 * ( month + 1 ) ) + day + 1720995L + b;

#else

    long lmonth = (long) iMonth, lday = (long) iDay, lyear = (long) iYear;

    // Adjust BC years
    if ( lyear < 0 )
        lyear++;

    jul_day = lday - 32075L +
        1461L * ( lyear + 4800L + ( lmonth - 14L ) / 12L ) / 4L +
        367L * ( lmonth - 2L - ( lmonth - 14L ) / 12L * 12L ) / 12L -
        3L * ( ( lyear + 4900L + ( lmonth - 14L ) / 12L ) / 100L ) / 4L;

#endif

    return jul_day;
}

//
//  Function      : JdToYmd
//
//  Author        : Todd Knarr
//
//  Creation date : 29 Nov 1995
//
//  Parameters    : long julian day, pointers to int year, month, day
//
//  Return values : none
//
//  Description   :
//
// Internal routine that reverses the conversion, turning a Julian
// day number into YMD values.
//
void Date::JdToYmd( const long lJD, int *piYear, int *piMonth, int *piDay )
{
#ifndef JULDATE_USE_ALTERNATE_METHOD

    long a, b, c, d, e, z, alpha;

    z = lJD;
    if ( z < 2299161L )
        a = z;
    else
    {
        alpha = (long) ( ( z - 1867216.25 ) / 36524.25 );
        a = z + 1 + alpha - alpha / 4;
    }
    b = a + 1524;
    c = (long) ( ( b - 122.1 ) / 365.25 );
    d = (long) ( 365.25 * c );
    e = (long) ( ( b - d ) / 30.6001 );
    *piDay = (int) b - d - (long) ( 30.6001 * e );
    *piMonth = (int) ( e < 13.5 ) ? e - 1 : e - 13;
    *piYear = (int) ( *piMonth > 2.5 ) ? ( c - 4716 ) : c - 4715;
    if ( *piYear <= 0 )
        *piYear -= 1;

#else

    long t1, t2, yr, mo;

    t1 = lJD + 68569L;
    t2 = 4L * t1 / 146097L;
    t1 = t1 - ( 146097L * t2 + 3L ) / 4L;
    yr = 4000L * ( t1 + 1L ) / 1461001L;
    t1 = t1 - 1461L * yr / 4L + 31L;
    mo = 80L * t1 / 2447L;
    *piDay = (int) ( t1 - 2447L * mo / 80L );
    t1 = mo / 11L;
    *piMonth = (int) ( mo + 2L - 12L * t1 );
    *piYear = (int) ( 100L * ( t2 - 49L ) + yr + t1 );

    // Correct for BC years
    if ( *piYear <= 0 )
        *piYear -= 1;

#endif

    return;
}

//
//  Function     : ToString
//
//  Author       : Todd Knarr
//
//  Creation date : 30 Nov 1995
//
//  Parameters    : pointer to string buffer to fill in
//
//  Return values : none
//
//  Description    :
//
// Formats the Date into an ASCII representation.
// This is the ASCII form of the long Julian day number.
// The string is a fixed-length 12-character string, including
// the NUL terminator;
//
void Date::ToString( char *szBuffer ) const
{
    int i;
    long Temp;

    Temp = lJulianDay;
    if ( Temp < 0L )
        szBuffer[0] = '-';
    else
        szBuffer[0] = '+';
    szBuffer[11] = '\0';
    for ( i = 10; i > 0; i-- )
    {
        szBuffer[i] = (char)(( Temp % 10L ) + '0');
        Temp /= 10;
    }

    return;
}

int Date::DayOfYear( void ) const
{
    int y, m, d;
    long soy;

    JdToYmd( lJulianDay, &y, &m, &d );
    soy = YmdToJd( y, 1, 1 );
    return (int) ( lJulianDay - soy + 1 );
}

//
//  Function      : ToSysTime
//
//  Author        : Todd Knarr
//
//  Creation date : 29 Nov 1995
//
//  Parameters    : none
//
//  Return values : converted result
//
//  Description   :
//
// Converts the date to a time_t value
// representing midnight of that date.
//
time_t Date::ToSysTime( void ) const
{
    struct tm tmRep;
    int y, m, d;
    time_t t;
    
    JdToYmd( lJulianDay, &y, &m, &d );
    if ( y < 1970 )
    {
        y = 70;
        m = 1;
        d = 1;
    }
    tmRep.tm_year = y - 1900 ;
    tmRep.tm_mon = m-1;
    tmRep.tm_mday = d;
    tmRep.tm_hour = 0;
    tmRep.tm_min = 0;
    tmRep.tm_sec = 0;
    tmRep.tm_isdst = 0;
    
    t = mktime( &tmRep );
    return t;
}

long Date::NumDaysTo(const Date& d)
{
	return d.lJulianDay - lJulianDay;
}

ostream& operator<<(ostream& os, const Date& d)
{
	os << d.Day() << "-" << d.Month() << "-" << d.Year();
	return os;
}

void Date::set(const string& str)
{
	tm tmTime;
	const char* s = str.c_str();
	ParseDateString((char*)s, &tmTime);

	int d,m,y;
	
	d = tmTime.tm_mday;
	m = tmTime.tm_mon+1;
	y = tmTime.tm_year;

	y = y + 1900;

	lJulianDay = YmdToJd( y, m, d);

//	cout << "year = " << y << endl;
//	cout << "month = " << m << endl;
//	cout << "day = " << d << endl;
}

void Date::set(int iDay, const int iMonth, const int iYear )
{
	lJulianDay = YmdToJd( iYear, iMonth, iDay );
}

// Author:	Michael Bone
// Date:	06-Mar-2000
// Name:	date.c
//
// Convert a string containing a date into a date type value.  It is assumed
// that the string contains the day, month, year information in a specified
// order (which is a parameter to the function).  It is further assumed that
// if not all components are provided then they can be defaulted based on the
// current system date.
//
// Most date formats will be recognised.  For example, if the order specified
// is day, month, year ("DMY"), then the following dates will be correctly
// recognised:
//
//    Provided     Parsed As
//
//    22-Dec-1999  22nd Decemeber 1999
//    31/03/99     31st March 1999     (depending on CENTURY_BREAK)
//    27/6-1955    27th June 1955
//    2-2-1976     2nd February 1976
//    2-Feb        2nd February 2010   (depending on current date)
//    15.09.58     15th September 2058 (depending on CENTURY_BREAK)
//    10           10th March 2010     (depending on current date)
//    1012         10th December 2000  (depending on current date)
//    101299       10th December 1999  (depending on CENTURY_BREAK)
//    10121998     10th December 1998
//
// The above examples assume a current date of 5th March 2010 and a
// "century break" setting of 60.
//
// Where a two digit date is provided it is expanded to four digits based
// on the "century break" (a parameter to the function).  For example, a
// century break of 60 indicates that if the current year (as given by the
// system date) is 2010 then the two digit years 00 to 69 will be interpreted
// as 2000 to 2069.  And the two digit years 70 to 99 will be interpreted as
// 1970 to 1999.
//
// See the AddCentury function for further details on how the century break
// setting works.
//
//______________________________________________________________________
// Change:	0
// Author:	Michael Bone
// Date:	06-Mar-2000
// Version: 1.00
//
// Initial development.
//______________________________________________________________________



//#include <windows.h>
#include <time.h>



#define DATE_ORDER    "DMY"  // Order of date components
#define CENTURY_BREAK 60     // Century break for two digit years
#define MAXIMUM_TOKEN 1024   // Maximum length of a parsed token



long MonthDays(long lMonth, long lYear);
long ConvertMonth(char* szMonth);
long AddCentury(long lTwoDigitYear, long lCurrentYear, long lCenturyBreak);



bool ParseDateString(char *szDateString, tm *tmTime)
{



	char c;                              // Current character of date string

	long lIndex;                         // Index into the date string
	long lTokenIndex;                    // Index into the token being constructed
	int  iTokenPosition;                 // Current token's "position" within date
	char szToken[MAXIMUM_TOKEN + 1];     // Token currently being parsed

	bool bNumeric;                       // Whether the current token is numeric
	bool bImpliedDelimiter;              // Whether the current delimiter is implied

	long lLength;                        // Length of the date string (alphanumeric characters)
	long lLength1;                       // Expected length of the first token (if numeric)
	long lLength2;                       // Expected length of the second token (if numeric)

	int  iDayPosition;                   // Position of the day component (based on date order)
	int  iMonthPosition;                 // Position of the month component (based on date order)
	int  iYearPosition;                  // Position of the year component (based on date order)

	struct tm *tmCurrentTime;            // Current system time (broken into components)
	time_t tCurrentTime;                   // Current system time (as a timer value)??

	long lDay;                           // Parsed day
	long lMonth;                         // Parsed month
	long lYear;                          // Parsed year
	long lCurrentYear;                   // Year given by the current system date

	char szDateOrder[] = DATE_ORDER;     // order of day, month and year
	long lCenturyBreak = CENTURY_BREAK;  // century break



	// Validate the specified date order.

	if (_stricmp(szDateOrder, "DMY") != 0 && _stricmp(szDateOrder, "MDY") != 0
	 && _stricmp(szDateOrder, "YMD") != 0 && _stricmp(szDateOrder, "YDM") != 0
	 && _stricmp(szDateOrder, "MYD") != 0 && _stricmp(szDateOrder, "DYM") != 0)
		return (false);


	
	// Validate the specified century break.

	if (lCenturyBreak < 0 || lCenturyBreak > 99) return (false);



	// Determine the number of alphanumeric characters in the provided
	// date string.

	lIndex = 0;
	lLength = 0;
	while (szDateString[lIndex]) if (isalnum(szDateString[lIndex++])) lLength++;



	// Do not attempt to parse an "empty" string.

	if (lLength == 0) return (false);



	// Get the components of the current date so that they can be used as
	// the default day, month and year.  These values will be overridden
	// as necessary by the following processing.
//?modded
	time(&tCurrentTime);
	tmCurrentTime = gmtime(&tCurrentTime); // ignore any errors here

	lDay = tmCurrentTime->tm_mday;
	lMonth = tmCurrentTime->tm_mon + 1;
	lYear = tmCurrentTime->tm_year + 1900;

	lCurrentYear = lYear;



	// Determine the positions and expected lengths of date components.
	// Note that the comparison against length when setting the expected
	// token lengths is performed so that dates such as "992212", "9922Dec"
	// and "19992212" will be correctly converted when the date order is
	// year, month, day.

	iDayPosition = (int)strcspn(szDateOrder, "dD");
	iMonthPosition = (int)strcspn(szDateOrder, "mM");
	iYearPosition = (int)strcspn(szDateOrder, "yY");

	lLength1 = (iYearPosition == 0 && lLength > 7) ? 4 : 2;  // numeric first token
	lLength2 = (iYearPosition == 1 && lLength > 7) ? 4 : 2;  // numeric second token



	// Parse the string, treating non-alphanumeric characters as delimeters
	// (including spaces, tabs, ',', '.', '/', '-', etc.).  Other changes
	// in the date string (such as from digits to letters) are treated as
	// "implied delimiters" and have the same effect as a real delimiter.

	lIndex = 0;
	lTokenIndex = 0;
	iTokenPosition = 0;
	bNumeric = true;

	do
	{

		// Extract the current character for examination.

		c = szDateString[lIndex];

		// A change from digits to alphabetic characters or from alphabetic
		// characters to digits has the same effect as a delimiter.  This
		// enables strings such as "21Dec99" to be correctly interpreted.
		// Also, encountering more than the requisite number of digits in
		// either of the first two tokens constitutes a delimiter (so that
		// strings such as "2112" and "211299" are correctly interpreted).

		bImpliedDelimiter =
			(isalpha(c) && lTokenIndex >= 1 && bNumeric)
		 ||	(isdigit(c) && lTokenIndex >= 1 && !bNumeric)
		 ||	(isdigit(c) && lTokenIndex >= lLength1 && bNumeric && iTokenPosition == 0)
		 ||	(isdigit(c) && lTokenIndex >= lLength2 && bNumeric && iTokenPosition == 1)
		 ||	(isalnum(c) && lTokenIndex >= MAXIMUM_TOKEN);

		if (!isalnum(c) || bImpliedDelimiter)
		{

			// If a token has been extracted then convert it into a number and
			// set the appropriate date component (day, month or year).  This
			// overrides the default date information that was previously set.
			// If this is the third token then no further parsing is required,
			// all further text in the date string is simply ignored.

			if (lTokenIndex > 0)
			{
				if (iDayPosition == iTokenPosition) lDay = atol(szToken);
				if (iMonthPosition == iTokenPosition)
					if (bNumeric) lMonth = atol(szToken);
					else lMonth = ConvertMonth(szToken);  // may return zero
				if (iYearPosition == iTokenPosition) lYear = atol(szToken);
				
				iTokenPosition++;
				if (iTokenPosition >= 3) break;  // no further parsing
				lTokenIndex = 0;
			}

			// Only move on to the next character if the current delimiter
			// was not an implied delimiter.  Note that an implied delimiter
			// can only be encountered while processing a token (ie. while
			// lTokenIndex is greater than zero).

			if (!bImpliedDelimiter) lIndex++;
			
		}
		else
		{

			// Add each non-delimiter character as it is encountered to the
			// token currently being constructed.

			szToken[lTokenIndex++] = c;
			szToken[lTokenIndex] = '\0';
			lIndex++;

			// If this is the first character of the token then determine
			// whether digits or alphabetic characters are being extracted.
			// Also determine whether the date component at this position
			// has the correct type (numeric or string).

			if (lTokenIndex == 1)
			{
				bNumeric = (bool)isdigit(c);
				if (iTokenPosition != iMonthPosition && !bNumeric) return (false);
			}

		}
		
	} while (c);



	// If no tokens were extracted then return unsuccessfully.

	if (iTokenPosition == 0) return (false);



	// Validate the year.

	if (lYear > 9999) return (false);



	// If the year is two digits, then assume that it requires the century
	// to be added, based on the century break setting.

	if (lYear < 100) lYear = AddCentury(lYear, lCurrentYear, lCenturyBreak);



	// Validate the day and month.

	if (lDay == 0 || (lMonth == 0 || lMonth > 12)) return (false);



	// Determine the number of days in the specified month (based on
	// the month and year).  Use this to further validate the day.

	if (lDay > MonthDays(lMonth, lYear)) return (false);



	// Return successfully (do not apply mktime to normalise the
	// time because it may not work with years after 2038).

	tmTime->tm_sec   = 0;
	tmTime->tm_min   = 0;
	tmTime->tm_hour  = 0;
	tmTime->tm_mday  = lDay;
	tmTime->tm_mon   = lMonth - 1;
	tmTime->tm_year  = lYear - 1900;
	tmTime->tm_wday  = 0;
	tmTime->tm_yday  = 0;
	tmTime->tm_isdst = tmCurrentTime->tm_isdst;
	return (true);



}



//______________________________________________________________________
// ConvertMonth
//
// Convert a string representing a month into the month number.
// Unsuccessful conversion is represented by returning zero.
//______________________________________________________________________

long ConvertMonth(char* szMonth)
{



	char *Month[12] = {"ja","f","mar","ap","may","jun","jul","au","s","o","n","d"};
	unsigned int i;
	unsigned int j;



	// If no month name was provided then return zero.

	if (strlen(szMonth) == 0) return (0);



	// Find the first, best match.  Note that the Month array contains
	// the minimum characters in each month name required to make a
	// unique identification of the month.

	for (j = 3; j > 0; j--)
		for (i = 0; i < 12; i++)
			if (_strnicmp(szMonth, Month[i], __min(strlen(Month[i]), j)) == 0)
				return (i + 1);



	// Return unsuccessfully.

	return (0);



}



//______________________________________________________________________
// MonthDays
//
// Determine how many days are in the given month of the given year.
// If this cannot be determined, zero is returned.
//______________________________________________________________________

long MonthDays(long lMonth, long lYear)
{



	long MonthLength[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
	long lMonthDays;


	
	// Return immediately if an invalid month has been provided.

	if (lMonth < 1 || lMonth > 12) return (0);



	// Determine the number of days in the month, taking into account
	// whether the specified year is a leap year.

	lMonthDays = MonthLength[lMonth - 1];
	if (lMonth == 2 && (lYear % 400 == 0 || (lYear % 4 == 0 && lYear % 100 != 0)))
		lMonthDays++;
	return (lMonthDays);



}



//______________________________________________________________________
// AddCentury
//
// Add the century two a two digit year based on the specified
// century break value.  The passed in year is returned if this
// fails.
//______________________________________________________________________

long AddCentury(long lTwoDigitYear, long lCurrentYear, long lCenturyBreak)
{



	// Provided with:
	//
	//    y = two digit year 0..99 (eg. 87)
	//    n = current year 0..9999 (eg. 2010)
	//    b = century break 0..99 (eg. 60)
	//
	// Must derive:
	//
	//    c = century to add 0..9900 (eg. 1900)
	//
	// And then return:
	//
	//    c+y = derived century plus two digit year (eg. 1987)
	//
	// The following formula holds, based on the definition of
	// century break:
	//
	//    n+(b-100) <= c+y <= n+(b-1)
	//
	// Or, re-arranging the first inequality:
	//
	//    c >= n+b-y-100
	//
	// Now, c must be a "whole" number century, such as 1700, 1800,
	// 1900, 2000, etc.  Therefore it is enough to compute n+b-y-100
	// and determine whether it works out to a "whole" number century
	// or if not, to find the next largest "whole" number century by
	// working forward (ie. rounding up).
	//
	// Another way of looking at this is that the value n+b-y-100 is
	// the smallest that c can be.  And the largest value c can take
	// on is n+b-y-1 (the other side of the inequality).  Therefore:
	//
	//    c = (n+b-y-100) + (a number between 0 and 99)
	//
	// such that c is a "whole" number century, eg. 1800, 1900, etc.



	long lCentury;



	// Verify that the year is two digits.  Return if it is not.

	if (lTwoDigitYear > 99) return (lTwoDigitYear);



	// Determine the century which must be added to the provided two
	// digit year (using the n+b-y-100 formula described above).

	lCentury = lCurrentYear + lCenturyBreak - lTwoDigitYear - 100;
	if (lCentury % 100 != 0) lCentury = lCentury - (lCentury % 100) + 100;
	return (lCentury + lTwoDigitYear);



}

